﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Workflows.Components.Entities.Distances.VectorDistances;
using Workflows.Components.Entities.Documents.Terms;

namespace Workflows.Components.Entities.Documents.Distances
{
    public class CosineDistanceCalculator : IFeatureVectorDistanceCalculator
    {
        #region IAttributeComparer Members

        //public string AssemblyFileName
        //{
        //    get { return this.GetType().Assembly.GetName().Name; }
        //    set {  }
        //}

        //public string TypeFullName
        //{
        //    get { return this.GetType().FullName; }
        //    set {  }
        //}

        public double CalculateDistance(object attr1, object attr2)
        {
            if (attr1.GetType() != typeof(BagOfWords) || attr2.GetType() != typeof(BagOfWords))
                throw new Exception("The attribute must be of type BagOfWords");

            BagOfWords termFreq1 = (BagOfWords)attr1;
            BagOfWords termFreq2 = (BagOfWords)attr2;
            if (termFreq1 == null || termFreq2 == null ||
                termFreq1.TermFreqs == null || termFreq2.TermFreqs == null ||
                termFreq1.TermFreqs.Count == 0 || termFreq2.TermFreqs.Count == 0)
                return double.PositiveInfinity;

            Dictionary<int, double> unionTermIDs1 = new Dictionary<int, double>();
            Dictionary<int, double> unionTermIDs2 = new Dictionary<int, double>();
            foreach (int termID1 in termFreq1.TermFreqs.Keys)
            {
                unionTermIDs1.Add(termID1, termFreq1.TermWeights[termID1] * termFreq1.TermFreqs[termID1]);
                if (termFreq2.TermFreqs.ContainsKey(termID1))
                {
                    unionTermIDs2.Add(termID1, termFreq2.TermFreqs[termID1] * termFreq2.TermWeights[termID1]);
                }
                else
                {
                    unionTermIDs2.Add(termID1, 0);
                }
            }
            foreach (int termID2 in termFreq2.TermFreqs.Keys)
            {
                if (!unionTermIDs2.ContainsKey(termID2))
                {
                    unionTermIDs2.Add(termID2, termFreq2.TermFreqs[termID2] * termFreq2.TermWeights[termID2]);
                }
                if (!unionTermIDs1.ContainsKey(termID2))
                {
                    unionTermIDs1.Add(termID2, 0);
                }
            }

            double length1 = 0;
            double length2 = 0;
            double svMul = 0;
            Dictionary<int, double> commonTerms = new Dictionary<int, double>();
            foreach (int termID in unionTermIDs1.Keys)
            {
                double freq1 = unionTermIDs1[termID];
                double freq2 = unionTermIDs2[termID];
                length1 += Math.Pow(freq1, 2);
                length2 += Math.Pow(freq2, 2);
                svMul += freq2 * freq1;

                if (freq1 > 0 && freq2 > 0)
                {
                    int count = 0;
                    if (termFreq1.TermFreqs.ContainsKey(termID) && termFreq2.TermFreqs.ContainsKey(termID))
                        count = Math.Min(termFreq1.TermFreqs[termID], termFreq2.TermFreqs[termID]);
                    else if (termFreq1.TermFreqs.ContainsKey(termID))
                        count = termFreq1.TermFreqs[termID];
                    else if (termFreq2.TermFreqs.ContainsKey(termID))
                        count = termFreq2.TermFreqs[termID];
                    commonTerms.Add(termID, count);
                }
            }

            length1 = Math.Sqrt(length1);
            length2 = Math.Sqrt(length2);

            double cosineDistance = svMul / (length1 * length2);
            double docLenScale = (double)commonTerms.Count * 2 /
                                 (termFreq1.TermFreqs.Count + termFreq2.TermFreqs.Count - commonTerms.Count);
            cosineDistance = cosineDistance * docLenScale;

            double contributingTermScale =
                TermWeightCalculator.TaminoWeightedTermScale(
                    termFreq1, termFreq2,
                    commonTerms);

            return cosineDistance * contributingTermScale;
        }

        #endregion
    }
}
